import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;

public class Server extends Thread implements BaseServer, MasterServer, SlaveServer {
    private BaseDataStorage storage;
    private Socket socket;
    private static String dataPath = "data.txt";
    private boolean isInMasterMode;
    public Journal log;
    private ArrayList<ServerDescriptor> slaveList;
    //private ServerDescriptor descriptor;

    // constructor
    public Server(Socket s, BaseDataStorage storage, Journal journal, ArrayList<ServerDescriptor> slaveList) {
        this.log = journal;
        this.slaveList = slaveList;
        this.storage = storage;
        this.socket = s;
        setDaemon(true);
        setPriority(NORM_PRIORITY);
        start();
    }

    public void run() {
        try {
            try {
                String result = "";
                System.out.println("got request\nstart processing...");
                Object object = Serialization.readObject(socket);
                if (object.getClass().equals(ServerDescriptor.class)) {
                    result = setServerMode((ServerDescriptor) object);
                } else if (object.getClass().equals(Storage.class)) {
                    for (Map.Entry<Character, Table> t : ((Storage)object).data.entrySet() ) {
                        storage.putTable(t.getKey(), t.getValue());
                    }
                    result = "ok";
                } else if(object.getClass().equals(Journal.class)) {
                    System.out.println("JOURNAL UPDATE");
                    updateDataFromJournal((Journal)object);
                } else if (object.getClass().equals(Query.class)) {
                    System.out.println("query request...");
                    Query q = (Query) object;
                    if (q.getQueryType() == QueryType.GET) {
                        result = getQueryResult(q);
                    } else if(q.getQueryType() == QueryType.ADD_SLAVE) {
                        System.out.println("adding new slave...");
                        result = executeSpecificQuery(q);
                    } else if (q.getQueryType() == QueryType.MOVE_DATA) {
                        System.out.println("moving data to new master...");
                        result = executeSpecificQuery(q);
                    } else {
                        result = executeQuery(q);
                    }

                }
                System.out.println(result);
                Serialization.writeObject(socket, result);
            } catch (ClassNotFoundException e) {
                System.out.print("Error! Wrong query's format!");
            } finally {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String executeSpecificQuery(Query query) throws IOException, ClassNotFoundException {
        String parameters[] = query.getQueryParameters();
        InetAddress address = InetAddress.getByName(parameters[0]);
        int port = Integer.parseInt(parameters[1]);
        switch (query.getQueryType()) {
            case ADD_SLAVE: {
                ServerDescriptor slaveDescriptor = new ServerDescriptor(address, port, false);
                slaveList.add(slaveDescriptor);
                Socket s = new Socket(address, port);
                Serialization.writeObject(s, slaveDescriptor);
                String result = Serialization.readObject(s).toString();
                s.close();
                return result;
            }
            case MOVE_DATA: {
                BaseDataStorage movingData = new Storage();
                for(char i = parameters[2].charAt(0); i <= parameters[3].charAt(0); i++) {
                    if (storage.getTable(i) != null) {
                        movingData.putTable(i,storage.removeTable(i));
                    }
                }
                Socket s = new Socket(address, port);
                Serialization.writeObject(s, movingData);
                String result = Serialization.readObject(s).toString();
                s.close();
                return result;
            } default:
                return "Error";
        }
    }

    public String executeQuery(Query query) {
        synchronized (log)
        {
            log.add(query);
            System.out.println("executing " + query.getQueryType() + " query");
            return storage.execute(query);
        }
    }

    public String getQueryResult(Query query) {
        System.out.println("get query result of " + query.getQueryType() + " query");
        return storage.getQueryResult(query);
    }

    public String setServerMode(ServerDescriptor descriptor) {
        isInMasterMode = descriptor.isMaster;
        if (isInMasterMode) {
            slaveList = new ArrayList<ServerDescriptor>();
        }
        return "Ok";
    }

    public void updateDataFromJournal(Journal journal) {
        for(Map.Entry<Date, Query> query : journal.getRecords().entrySet()) {
                storage.execute(query.getValue());
        }
    }
}
